home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / Apps / ScreenSavers / BackSpaceViews / SparseLifeView.BackModule / SparseLifeView.m < prev    next >
Text File  |  1995-06-12  |  22KB  |  713 lines

  1. // SparseLifeView by David Bau
  2. //
  3. // I feel silly even saying this, but please don't charge for this
  4. // module or any modification of it, and please give credit where
  5. // credit is due.
  6. // 
  7. // This code is shamelessly ripped off of Sam Streeper's Life module.
  8. // Here is Sam's original description.
  9. //
  10. // ****
  11. // Life is the classical demonstration of cellular automata.
  12. // It was originally created as a simplisting simulation of the dynamics
  13. // of living communities.  I've always thought these things are pretty
  14. // cool; though the algorithm behind Life is exceedingly simple,
  15. // getting good performance seems to require different hacks for
  16. // the display architecture of every machine.
  17. // ...
  18. // Living cell with < 2 neighbors    -> dies of isolation
  19. // Living cell with 2 or 3 neighbors -> lives
  20. // Living cell with > 3 neighbors    -> dies of overcrowding
  21. // empty cell with 3 neighbors       -> life is created (reproduction)
  22. // ...
  23. // ****
  24. // 
  25. // I've changed several things.  First, the method for computing and
  26. // drawing cells was changed to a sparse algorithm that traverses a
  27. // list of live cells.  This way empty cells are looked at as little
  28. // as possible, which is a big win when the field is mostly empty.
  29. // As a nice side effect, the squares get drawn in a (more or less)
  30. // uniformly random order, which gives a smoother visual effect
  31. // without any left-to-right flickering.
  32. //
  33. // The drawing-buffering code was cleaned up.  Smaller lists of rects
  34. // are used, but one for each color.  No noticable performance hit.
  35. //
  36. // A hack in drawSelf was added to work around the double-refresh
  37. // problem when the screensaver first kicks in. (countDown!=ITERATIONS)
  38. //
  39. // The initLife seeding algorithm was changed.  Now it puts inital
  40. // cells in a small circular patch, leaving the rest of the field
  41. // empty, which is much better for the sparse algorithm.  For big
  42. // windows, the circular patch appears in a random starting place.
  43. //
  44. // Stasis checking was changed.  Now it automatically deduces any
  45. // stasis period (up to the size of the stasis buffer), but it takes
  46. // more generations for stasis to be detected.
  47. //
  48. // The color table was generalized to dish out NXColors, instead of
  49. // just hues.  Cell size was generalized.
  50. //
  51. // The control panel was souped up.  Now the color table can be
  52. // changed by the user.  The cell sizes can be changed.  The panel
  53. // animation appears (slowly) partially obscured by the panel
  54. // controls.  The panel animation can be stepped manually by the
  55. // user. Defaults are saved in the user's defaults database.
  56. // Programming the panel was the hardest part, but the result
  57. // looks pretty nice.  I'm beginning to appreciate IB.
  58. //
  59. // 10/20/93 fixes and improvements: used perform:with:afterDelay to
  60. // credits button remove itself correctly.  Also used the same method
  61. // to do the panel animation in a more polite, lazy way.  Changed
  62. // "step" button to a continuous button.  Fixed the timed panel
  63. // animation so it doesn't draw if the view is gone from the window.
  64. //
  65. // 10/23/93 Changed sizing buttons to a matrix of buttons.  Stop
  66. // timed panel animation if already doing an animation somewhere else
  67. // (window or background) by making sure countDown isn't changing.
  68. //
  69. // 10/27/93 Added LIVEEDGE boundary convention.  (Later removed.)
  70. // Made it so drawing can go right up to the edge of the window,
  71. // using a random offset when it can't completely fill it. Removed
  72. // unneeded calls to flushColor to avoid NXSetColor calls.
  73. //
  74. // 10/29/93 Added DEEPEDGE, so field can extend beyond the visible screen.
  75. // turned off LIVEEDGE feature.  Fine tuned seeding parameters.
  76. //
  77. // 11/05/93 Changed MINCELLSIZE to 2, which can be obtained by a dwrite
  78. // to SparseLifeCellSize only.  Played with matrix of radio buttons for
  79. // so none are highlighed when cellSize matches none of them (e.g.=2),
  80. // but so that once the user starts selecting them, they go into
  81. // setEmptySelectionEnabled:NO mode.
  82. //
  83. // 11/12/93 Changed seeding to make two patches when field is very large.
  84. //
  85. // If you add any neat stuff to this module or derive anything
  86. // interesting from it, I'd be interested to see!
  87. //
  88. // I'll probably be bau@cs.cornell.edu for a while.
  89. //
  90. // David Bau 11/12/93
  91. // 777 South Avenue, Weston, MA 02193
  92.  
  93.  
  94.  
  95. #import <appkit/appkit.h>
  96. #import <defaults/defaults.h>
  97. #import <libc.h>
  98. #import <time.h>
  99. #import <dpsclient/wraps.h>
  100. #import "SparseLifeView.h"
  101. #import "Thinker.h"
  102.  
  103.  
  104. /* the kind of Life view that appears in the control panel */
  105. @implementation StaticSparseLifeView
  106.  
  107. - initLife
  108. {
  109.     int x,y,count;
  110.     /* frame the field 10 cells bigger than the view */
  111.     ncols=MIN((bounds.size.width/cellSize+2+2*DEEPEDGE+10),MAXCOLS);
  112.     nrows=MIN((bounds.size.height/cellSize+2+2*DEEPEDGE+10),MAXROWS);
  113.     xoffset=(bounds.size.width-ncols*cellSize)/2;
  114.     yoffset=(bounds.size.height-nrows*cellSize)/2;
  115.  
  116.     /* clear the field */
  117.     [self clearLife];
  118.  
  119.     /* use uniform random seeding */
  120.     for (count=ncols*nrows/5; count; count--) {
  121.         x=random()%(ncols-2)+1;
  122.         y=random()%(nrows-2)+1;
  123.         if (Grid[x+y*MAXCOLS].color==(-1)) {
  124.             Grid[x+y*MAXCOLS].color=0;
  125.             Grid[x+y*MAXCOLS].next=ifirst;
  126.             ifirst=x+y*MAXCOLS;
  127.         }
  128.         if (Grid[x+y*MAXCOLS].color<COLORS-1) Grid[x+y*MAXCOLS].color++;
  129.     }
  130.  
  131.     /* don't go for too many generations.  Seems it would be boring. */
  132.     countDown = 1000;
  133.  
  134.     return self;
  135. }
  136.  
  137. /* main view can tell panel view what colors to use */
  138. - setYoungColor:(NXColor)yc MediumColor:(NXColor)mc OldColor:(NXColor)oc
  139. {
  140.     youngColor=yc;
  141.     mediumColor=mc;
  142.     oldColor=oc;
  143.     [self computeColors];
  144.     return self;
  145. }
  146.  
  147. /* main view can tell panel what cell size to use */
  148. - setLifeCellSize:(int)cs;
  149. {
  150.     cellSize=cs;
  151.     [self setupSquareBuffer];
  152.     return self;
  153. }
  154.  
  155. @end
  156.  
  157.  
  158.  
  159.  
  160.  
  161.  
  162. @implementation SparseLifeView
  163.  
  164. /* here's where the sparse computation and drawing is done */
  165. - oneStep
  166. {
  167.     int icur, *picur;
  168.     int checksum = 0;
  169.  
  170.     /* finished */
  171.     if (--countDown < 0)
  172.     {
  173.         [self initLife];
  174.         [self display];
  175.     }
  176.  
  177.     /* pass one: count up neighbors.  Hope gcc does cse well... */
  178. #define CONTRIBUTE_TO_GRID(iadj)         \
  179.     if (Grid[iadj].color==(-1)) {        \
  180.         Grid[iadj].color=0;              \
  181.         Grid[iadj].next=ifirst;          \
  182.         ifirst=iadj;                     \
  183.     }                                    \
  184.     Grid[iadj].neighbors++;
  185.  
  186.     for (icur=ifirst; icur>=0; icur=Grid[icur].next) {
  187.         /* contribute to the west */
  188.         CONTRIBUTE_TO_GRID(icur-1);
  189.         CONTRIBUTE_TO_GRID(icur-1-MAXCOLS);
  190.         CONTRIBUTE_TO_GRID(icur-MAXCOLS);
  191.         CONTRIBUTE_TO_GRID(icur+1-MAXCOLS);
  192.         CONTRIBUTE_TO_GRID(icur+1);
  193.         CONTRIBUTE_TO_GRID(icur+1+MAXCOLS);
  194.         CONTRIBUTE_TO_GRID(icur+MAXCOLS);
  195.         CONTRIBUTE_TO_GRID(icur-1+MAXCOLS);
  196.     }
  197. #undef CONTRIBUTE_TO_GRID
  198.  
  199.     /* pass two: redraw and prune */
  200.     picur=&ifirst;
  201.     while (*picur>=0) {
  202.         /* a cell only if 2 neighbors and was a cell, or 3 neighbors */
  203.         if (((Grid[*picur].neighbors==2 && Grid[*picur].color) ||
  204.              Grid[*picur].neighbors==3)) {
  205.             if (Grid[*picur].color<COLORS-1) {
  206.                 Grid[*picur].color++;
  207.                 [self putSquare:(*picur)%MAXCOLS :(*picur)/MAXCOLS
  208.                       Color: Grid[*picur].color];
  209.             }
  210.             Grid[*picur].neighbors=0;
  211.             picur=&(Grid[*picur].next); /* advance */
  212.         } else {
  213.             if (Grid[*picur].color) {
  214.                 [self putSquare:(*picur)%MAXCOLS :(*picur)/MAXCOLS
  215.                           Color: 0];
  216.                 checksum += (*picur * *picur);
  217.             }
  218.             Grid[*picur].color=(-1);
  219.             Grid[*picur].neighbors=0;
  220.             *picur=Grid[*picur].next; /* delete from list and advance */
  221.         }
  222.     }
  223.  
  224.     /* empty anything left in the drawing buffers */
  225.     [self flushSquares];
  226.     
  227.     /* check for termination if things are cycling */
  228.     [self checkStasis:checksum];
  229.  
  230.     return self;
  231. }
  232.  
  233.  
  234. - drawSquares
  235. {
  236.     int icur;
  237.  
  238.     for (icur=ifirst; icur>=0; icur=Grid[icur].next) {
  239.         [self putSquare:(icur%MAXCOLS):(icur/MAXCOLS)
  240.               Color:Grid[icur].color];
  241.     }
  242.     [self flushSquares];
  243.     return self;
  244. }
  245.  
  246. - putSquare:(int)x :(int)y Color:(int)color
  247. {
  248.     NXRect *newsquare;
  249. #if DEEPEDGE
  250.     if (y<(1+DEEPEDGE) || y>=nrows-(1+DEEPEDGE) ||
  251.         x<(1+DEEPEDGE) || x>=ncols-(1+DEEPEDGE)) return self;
  252. #endif
  253.     if (squareCount[color]>=SQUAREBLOCK) [self flushColor:color];
  254.     newsquare = squareBuffer[color]+squareCount[color];
  255.     newsquare->origin.x=x*cellSize+xoffset;
  256.     newsquare->origin.y=y*cellSize+yoffset;
  257.     squareCount[color]++;
  258.  
  259.     return self;
  260. }
  261.  
  262. - flushColor:(int)color
  263. {
  264.     if (squareCount[color]) {
  265.         NXSetColor(colorTable[color]);
  266.         NXRectFillList(squareBuffer[color],squareCount[color]);
  267.         squareCount[color]=0;
  268.     }
  269.     return self;
  270. }
  271.  
  272. - flushSquares
  273. {
  274.     int color;
  275.     for (color = 0; color<COLORS; color++) {
  276.         [self flushColor: color];
  277.     }
  278.     return self;
  279. }
  280.         
  281. - drawSelf:(const NXRect *)rects :(int)rectCount
  282. {
  283.     if (!rects || !rectCount) return self;
  284.     PSsetgray(0);
  285.     NXRectFill(rects);
  286.     if (countDown!=ITERATIONS) [self drawSquares];
  287.     return self;
  288. }
  289.  
  290. - (const char *) windowTitle
  291. {    return "Sparse Life";
  292. }
  293.  
  294. - setupSquareBuffer
  295. {
  296.     int i,b;
  297.     for (i=0; i<COLORS; i++) {
  298.         squareCount[i]=0;
  299.         for (b=0; b<SQUAREBLOCK; b++) {
  300.             squareBuffer[i][b].origin.x=0;
  301.             squareBuffer[i][b].origin.y=0;
  302.             squareBuffer[i][b].size.width=cellSize;
  303.             squareBuffer[i][b].size.height=cellSize;
  304.         }
  305.     }
  306.     return self;
  307. }    
  308.  
  309. - initFrame:(const NXRect *)frameRect
  310. {
  311.     [super initFrame:frameRect];
  312.     [self getLifeDefaults];
  313.         [self computeColors];
  314.         srandom(time(NULL));
  315.  
  316.         [self setupSquareBuffer];
  317.     [self initLife];
  318.  
  319.     return self;
  320. }
  321.  
  322. static void ColorToString(NXColor color, char *str)
  323. {
  324.     sprintf(str,"%f %f %f",NXRedComponent(color),
  325.             NXGreenComponent(color),NXBlueComponent(color));
  326. }
  327.  
  328. static NXColor ColorFromString(const char *str)
  329. {
  330.     NXColor color;
  331.     float r,g,b;
  332.     sscanf(str,"%f %f %f",&r,&g,&b);
  333.     r=(r<0.0 ? 0.0 : (r>1.0 ? 1.0 : r));
  334.     g=(g<0.0 ? 0.0 : (g>1.0 ? 1.0 : g));
  335.     b=(b<0.0 ? 0.0 : (b>1.0 ? 1.0 : b));
  336.     color=NXConvertRGBToColor(r,g,b);
  337.     return color;
  338. }
  339.     
  340. - getLifeDefaults
  341. {
  342.     static NXDefaultsVector SparseLifeDefaults = {
  343.         {"SparseLifeYoungColor", "0.083331 0.000000 1.000000"},
  344.         {"SparseLifeMediumColor","0.916669 1.000000 0.000000"},
  345.         {"SparseLifeOldColor",   "0.400005 0.000000 0.066668"},
  346.         {"SparseLifeCellSize",   "8"},
  347.         {NULL}
  348.     };
  349.     
  350.     NXRegisterDefaults("BackSpace",SparseLifeDefaults);
  351.     youngColor=
  352.       ColorFromString(NXGetDefaultValue("BackSpace","SparseLifeYoungColor"));
  353.     mediumColor=
  354.       ColorFromString(NXGetDefaultValue("BackSpace","SparseLifeMediumColor"));
  355.     oldColor=
  356.       ColorFromString(NXGetDefaultValue("BackSpace","SparseLifeOldColor"));
  357.     sscanf(NXGetDefaultValue("BackSpace","SparseLifeCellSize"),"%d",&cellSize);
  358.     if (cellSize<MINCELLSIZE) cellSize=MINCELLSIZE;
  359.     if (cellSize>MAXCELLSIZE) cellSize=MAXCELLSIZE;
  360.  
  361.     return self;
  362. }
  363.  
  364.  
  365. - sizeTo:(NXCoord)width :(NXCoord)height
  366. {
  367.     [super sizeTo:width :height];
  368.     [self initLife];
  369.     return self;
  370. }
  371.  
  372. /* clear the field and do some random seeding */
  373. - initLife
  374. {
  375.     int x,y,xr,yr,xo,yo,xa,ya,i,count,repeat,size;
  376.  
  377.     /* frame the field */
  378.     ncols = MIN((bounds.size.width/cellSize)+2+2*DEEPEDGE,MAXCOLS);
  379.     nrows = MIN((bounds.size.height/cellSize)+2+2*DEEPEDGE,MAXROWS);
  380.     xoffset = random()%(1+(int)(bounds.size.width-
  381.                         (ncols-2-2*DEEPEDGE)*cellSize))-(1+DEEPEDGE)*cellSize;
  382.     yoffset = random()%(1+(int)(bounds.size.height-
  383.                         (nrows-2-2*DEEPEDGE)*cellSize))-(1+DEEPEDGE)*cellSize;
  384.  
  385.     /* clear the field */
  386.     [self clearLife];
  387.  
  388.     /* do some seeding: two patches if big field, one patch if smaller */
  389.     if (ncols>256 && nrows>256) {
  390.         size=96;
  391.         repeat=2;
  392.     } else {
  393.         size=128;
  394.         repeat=1;
  395.     }
  396.     while (repeat) {
  397.         if ((ncols-3)>size){xr=size/8+1;xo=1+random()%((ncols-3)-size+1);xa=1;}
  398.         else {xr=((ncols-3)/8)+1; xo=1; xa=((ncols-3)%8)+1;}
  399.         if ((nrows-3)>size){yr=size/8+1;yo=1+random()%((nrows-3)-size+1);ya=1;}
  400.         else {yr=((nrows-3)/8)+1; yo=1; ya=((nrows-3)%8)+1;}
  401.         for (count=MAX(xr*yr*4,100); count; count--) {
  402.             /* add up 8 rolls of dice for each coordinate */
  403.             for (i=0, x=xo+random()%xa; i<8; i++) x+=random()%xr;
  404.             for (i=0, y=yo+random()%ya; i<8; i++) y+=random()%yr;
  405.             if (Grid[x+y*MAXCOLS].color==(-1)) {
  406.                 Grid[x+y*MAXCOLS].color=0;
  407.                 Grid[x+y*MAXCOLS].next = ifirst;
  408.                 ifirst=x+y*MAXCOLS;
  409.             }
  410.             if (Grid[x+y*MAXCOLS].color<COLORS-1) Grid[x+y*MAXCOLS].color++;
  411.         }
  412.         repeat--;
  413.     }
  414.  
  415.     countDown = ITERATIONS;
  416.     
  417.     return self;
  418. }
  419.  
  420. - clearLife
  421. {
  422.     int x,y,i;
  423.  
  424.     /* empty linked list of interesting cells */
  425.     ifirst = -1;
  426.  
  427.     /* clear the field */
  428.     for (x=0; x<ncols; x++) {
  429.     for (y=0; y<nrows; y++) {
  430.             if (x==0 || y==0 || x==ncols-1 || y==nrows-1) {
  431.                 Grid[x+y*MAXCOLS].color=(-2);
  432.             } else {
  433.                 Grid[x+y*MAXCOLS].neighbors=0;
  434.                 Grid[x+y*MAXCOLS].color=(-1);
  435.             }
  436.     }
  437.     }
  438.  
  439.     /* init stasis array */
  440.     for (i=0; i<STATSIZE; i++) stasis[i] = i;
  441.     strack = 0;
  442.     sindex = 0;
  443.     spass = 0;
  444.  
  445.     return self;
  446. }
  447.  
  448.  
  449. /* check for stasis (any period of repetition up to STATSIZE) */
  450. - checkStasis:(int)checksum
  451. {
  452.     int i;
  453.     if (!strack || stasis[(sindex+STATSIZE-strack)%STATSIZE]!=checksum) {
  454.         spass=0;
  455.         strack=0;
  456.         for (i=0; i<STATSIZE; i+=STATIVAL) {
  457.             if (stasis[i]==checksum) {
  458.                 if (i==sindex) strack=STATSIZE;
  459.                 else strack=(sindex+STATSIZE-i)%STATSIZE;
  460.                 break;
  461.             }
  462.         }
  463.     } else {
  464.         spass++;
  465.         if (spass>=STATIVAL) countDown=0; /* must match STATIVAL generations */
  466.                                           /* STATIVAL should be more than 2 */
  467.     }
  468.  
  469.     stasis[sindex++] = checksum;
  470.     if (sindex>=STATSIZE) sindex = 0;
  471.     return self;
  472. }
  473.  
  474. /* given old-cell color, young-cell color, and medium-cell color, */
  475. /* linearly interpolate the whole color table in HSB space */
  476. - computeColors
  477. {
  478.     float yhue, ysat, ybri;
  479.     float mhue, msat, mbri;
  480.     float ohue, osat, obri;
  481.     float chue;
  482.     int i;
  483.  
  484.     NXConvertColorToHSB(youngColor, &yhue, &ysat, &ybri);
  485.     NXConvertColorToHSB(mediumColor, &mhue, &msat, &mbri);
  486.     NXConvertColorToHSB(oldColor, &ohue, &osat, &obri);
  487.  
  488.     /* hue space is circular, so decide which direction to go */
  489.     /* (take the shortest path out of the two possible) */
  490.     if (yhue>mhue && yhue-mhue>0.5) yhue -= 1.0;
  491.     else if (mhue>yhue && mhue-yhue>0.5) yhue += 1.0;
  492.     if (ohue>mhue && ohue-mhue>0.5) ohue -= 1.0;
  493.     else if (mhue>ohue && mhue-ohue>0.5) ohue += 1.0;
  494.  
  495.     colorTable[0]=NXConvertGrayToColor(NX_BLACK);
  496.     /* interpolate from young to medium */
  497.     for (i=1; i<COLORS/3; i++) {
  498.         chue=(float)(yhue*(COLORS/3-i)+mhue*(i-1))/(COLORS/3-1);
  499.         if (chue<0.0) chue += 1.0;
  500.         else if (chue>1.0) chue -= 1.0;
  501.         colorTable[i]=NXConvertHSBToColor(chue,
  502.             (float)(ysat*(COLORS/3-i)+msat*(i-1))/(COLORS/3-1),
  503.             (float)(ybri*(COLORS/3-i)+mbri*(i-1))/(COLORS/3-1));
  504.     }
  505.     /* from medium to old */
  506.     for (i=COLORS/3; i<COLORS; i++) {
  507.         chue=(float)(mhue*(COLORS-1-i)+ohue*(i-COLORS/3))/(COLORS-1-COLORS/3);
  508.         if (chue<0.0) chue += 1.0;
  509.         else if (chue>1.0) chue -= 1.0;
  510.         colorTable[i]=NXConvertHSBToColor(chue,
  511.             (float)(msat*(COLORS-1-i)+osat*(i-COLORS/3))/(COLORS-1-COLORS/3),
  512.             (float)(mbri*(COLORS-1-i)+obri*(i-COLORS/3))/(COLORS-1-COLORS/3));
  513.     }
  514.  
  515.     /* pass colors on to panel also, if needed */
  516.     if (panelLifeView && sharedInspectorPanel) {
  517.         [panelLifeView setYoungColor:youngColor
  518.                        MediumColor:mediumColor
  519.                        OldColor:oldColor];
  520.     }
  521.  
  522.     return self;
  523. }
  524.  
  525. /* quick update called when color has been changed */
  526. - updateViews
  527. {
  528.     /* update the panel view, if needed */
  529.     if (sharedInspectorPanel) [sharedInspectorPanel display];
  530.  
  531.     /* update myself */
  532.     [self lockFocus];
  533.     [self drawSquares];
  534.     [self unlockFocus];
  535.     return self;
  536. }
  537.  
  538. - inspector:sender
  539. {
  540.     char buf[MAXPATHLEN];
  541.     if (!sharedInspectorPanel) {
  542.         sprintf(buf,"%s/SparseLife.nib",[sender moduleDirectory:"SparseLife"]);
  543.         [NXApp loadNibFile:buf owner:self withNames:NO];
  544.         /* initialize some of the panel objects... */
  545.         if (panelYoungColorWell) [panelYoungColorWell setColor:youngColor];
  546.         if (panelMediumColorWell) [panelMediumColorWell setColor:mediumColor];
  547.         if (panelOldColorWell) [panelOldColorWell setColor:oldColor];
  548.         if (panelSizeMatrix) {
  549.             [panelSizeMatrix setEmptySelectionEnabled:YES];
  550.             if (![panelSizeMatrix selectCellWithTag:cellSize]) {
  551.                 [panelSizeMatrix selectCellAt:-1:-1];
  552.             } else {
  553.                 [panelSizeMatrix setEmptySelectionEnabled:NO];
  554.             }
  555.         }
  556.         if (panelStepButton) {
  557.             [panelStepButton sendActionOn:NX_MOUSEDOWNMASK];
  558.             [panelStepButton setContinuous:YES];
  559.             [panelStepButton setPeriodicDelay:PANELTIME/1000.0
  560.                              andInterval:PANELTIME/1000.0];
  561.         }
  562.         [self computeColors]; /* updates color table inside panelLifeView */
  563.     }
  564.     return sharedInspectorPanel;
  565. }
  566.  
  567. - inspectorInstalled
  568. {
  569.     int i;
  570.     [self hideCredits:self];
  571.     if (sharedInspectorPanel) [sharedInspectorPanel display];
  572.     if (panelLifeView && sharedInspectorPanel) {
  573.         installedCountDown=countDown;
  574.         for (i=1; i<=5; i++) {
  575.             [self perform:@selector(doSingleStep:) with:self
  576.                   afterDelay:PANELTIME*i cancelPrevious:(i==1)];
  577.         }
  578.     }
  579.     return self;
  580. }
  581.  
  582. - takeYoungColorFrom:sender
  583. {
  584.     char str[80];
  585.     youngColor=[(NXColorWell *)sender color];
  586.     [self computeColors];
  587.     [self updateViews];
  588.     ColorToString(youngColor,str);
  589.     NXWriteDefault("BackSpace","SparseLifeYoungColor",str);
  590.     return self;
  591. }
  592.  
  593. - takeMediumColorFrom:sender
  594. {
  595.     char str[80];
  596.     mediumColor=[(NXColorWell *)sender color];
  597.     [self computeColors];
  598.     [self updateViews];
  599.     ColorToString(mediumColor,str);
  600.     NXWriteDefault("BackSpace","SparseLifeMediumColor",str);
  601.     return self;
  602. }
  603.  
  604. - takeOldColorFrom:sender
  605. {
  606.     char str[80];
  607.     oldColor=[(NXColorWell *)sender color];
  608.     [self computeColors];
  609.     [self updateViews];
  610.     ColorToString(oldColor,str);
  611.     NXWriteDefault("BackSpace","SparseLifeOldColor",str);
  612.     return self;
  613. }
  614.  
  615. - doSizeMatrix:sender
  616. {
  617.     char str[80];
  618.     int newSize;
  619.     id selected;
  620.     selected=[sender selectedCell];
  621.     if (selected) newSize=[selected tag];
  622.     if (!selected || cellSize==newSize ||
  623.         newSize<MINCELLSIZE || newSize>MAXCELLSIZE) {
  624.         if (![panelSizeMatrix selectCellWithTag:cellSize]) {
  625.             [panelSizeMatrix selectCellAt:-1:-1];
  626.         }
  627.         return self;
  628.     }
  629.     if ([sender isEmptySelectionEnabled]) [sender setEmptySelectionEnabled:NO];
  630.     cellSize=newSize;
  631.     sprintf(str,"%d",cellSize);
  632.     NXWriteDefault("BackSpace","SparseLifeCellSize",str);
  633.     [self setupSquareBuffer];
  634.     [self initLife];
  635.     [self display];
  636.     if (panelLifeView) {
  637.         [panelLifeView setLifeCellSize:cellSize];
  638.         [panelLifeView initLife];
  639.     }
  640.     if (sharedInspectorPanel) [sharedInspectorPanel display];
  641.     return self;
  642. }
  643.  
  644. - doSingleStep:sender
  645. {
  646.     /* don't animate panel if animating another window */
  647.     if (sender==self && installedCountDown!=countDown) return self;
  648.  
  649.     /* should not do the panel animation if the view is not loaded or */
  650.     /* if it has been removed from the inspector window */
  651.     if (panelLifeView && sharedInspectorPanel &&
  652.         [panelLifeView canDraw] && [sharedInspectorPanel canDraw]) {
  653.         [panelLifeView lockFocus];
  654.         [panelLifeView oneStep];
  655.         [panelLifeView unlockFocus];
  656.         [sharedInspectorPanel display];
  657.     }
  658.     return self;
  659. }
  660.  
  661. - doRestart:sender
  662. {
  663.     if (panelLifeView) [panelLifeView initLife];
  664.     if (sharedInspectorPanel) [sharedInspectorPanel display];
  665.     return self;
  666. }
  667.  
  668.  
  669. - showCredits:sender
  670. {
  671.     if (panelCreditsView && sharedInspectorPanel) {
  672.         if ([panelCreditsView isDescendantOf:sharedInspectorPanel]) {
  673.             [panelCreditsView removeFromSuperview];
  674.         }
  675.         [sharedInspectorPanel addSubview:panelCreditsView];
  676.         [sharedInspectorPanel display];
  677.     }
  678.     return self;
  679. }
  680.  
  681. - hideCredits:sender
  682. {
  683.     if (panelCreditsView && sharedInspectorPanel) {
  684.         if ([panelCreditsView isDescendantOf:sharedInspectorPanel]) {
  685.             [panelCreditsView removeFromSuperview];
  686.         }
  687.         [sharedInspectorPanel display];
  688.     }
  689.     return self;
  690. }
  691.  
  692. /* these methods are needed because we should not rearrange the view */
  693. /* hierarchy while a button is active; we queue the rearranging to be */
  694. /* done later as an event, when nothing is locked on the view */
  695.  
  696. - doShowCredits:sender
  697. {
  698.     [self perform:@selector(showCredits:)
  699.           with:self afterDelay:0 cancelPrevious:YES];
  700.     return self;
  701. }
  702.  
  703. - doHideCredits:sender
  704. {
  705.     [self perform:@selector(hideCredits:)
  706.           with:self afterDelay:0 cancelPrevious:YES];
  707.     return self;
  708. }
  709.  
  710. @end
  711.  
  712.  
  713.